<html t:ref="shell.html">
<li class="active" t:slot="nav3" t:ref=""/>
<div t:slot="body">
    <h4 style="margin-top: 0">1. <b>框架结构</b></h4>
    <p>
        <a href="http://en.wikipedia.org/wiki/Model-view-controller">MVC</a>概念在web开发领域已深入人心并广泛获得业界认可，
        WebWheel框架也是基于这个概念之上的，并尽力诠释这一概念，尤其致力于分离表现与业务逻辑。
    </p>
    <p>
        <img src="imgs/mvc.jpg">
    </p>
    <p>
        WebWheelFilter作为框架与容器的结合点，负责对HTTP请求进行分发，根据在<code>web.xml</code>配置以及框架的配置
        （在<code>Main</code>的子类中）决定将HTTP请求路由到合适的动作方法之上。
    </p>
    <p>
        动作方法为普通的java方法，WebWheelFilter将HTTP参数进行相应的转换之后传递给动作方法。
        内置的转换过程可将HTTP参数转换为java基本类型、java基本类型数组、java对象等。
        转换过程可以由应用程序进行扩展，以实现更为方便的业务相关的参数转换。
    </p>
    <p>
        动作方法可拥有返回值作为业务逻辑的响应结果，此返回值交给预先注册的结果解释器进行解释（例如渲染页面、下载文件、页面跳转等等）。
        结果解释器可由应用程序进行扩展，增加或修改结果解释器可增强动作方法的表现力，并帮助业务与表现的解耦。
    </p>
    <h4>2. <b>应用入口类</b></h4>
    <p>
        基于WebWheel的应用程序需要定义一个全局入口类，此类将在应用的生命周期内被框架调用，
        应用开发者应当在此类中进行应用程序的初始化和清理工作，同时也能通过覆写一些方法来改变框架的行为或添加一些处理。入口类需要继承自
        <code>cn.webwheel.Main</code>类，此类定义如下：
    </p>
    <pre class="prettyprint">abstract public class Main {

    /**
     * 应用程序的servlet上下文，子类可直接引用
     */
    protected ServletContext servletContext;

    /**
     * 结果解释器映射关系，结果类类型 -> 结果解释器
     */
    protected Map&lt;Class, ResultInterpreter> interpreterMap = new HashMap&lt;Class, ResultInterpreter>();

    /**
     * 动作方法信息映射关系，路径 -> 动作方法信息，WebWheelFilter根据此信息进行HTTP请求路由
     */
    protected Map&lt;String, ActionInfo> actionMap = new HashMap&lt;String, ActionInfo>();

    /**
     * HTTP地址重定向映射关系，路径正则表达式 -> 目标路径，可根据此映射关系支持友好url
     */
    protected Map&lt;Pattern, String> rewritePatterns = new HashMap&lt;Pattern, String>();

    /**
     * 默认的动作方法参数转换配置，例如HTTP请求的文本编码等
     */
    protected final SetterConfig defSetterConfig = new SetterConfig();

    /**
     * 动作类的创建过程，每一个HTTP请求将创建一个动作类，默认通过Class.newInstance()方法创建，应用程序可以覆写此方法实现IOC
     */
    public &lt;T> T createAction(WebContext ctx, Class&lt;T> type);

    /**
     * 应用初始化过程，此方法中设置了常用的结果解释器以及HTTP参数转换器，所以一般情况下，应用程序在覆写此方法后要调用super.init()
     * 方法使默认配置生效。
     */
    protected void init();

    /**
     * 应用关闭过程，应用程序可以覆写此方法，并在其内部进行资源清理工作。
     */
    protected void destroy();

    /**
     * 结果对象处理过程，默认是根据结果类型找到已注册的结果解释器对结果进行解释。
     * 返回是否处理了结果，如果返回false未处理结果，则HTTP请求将透过WebWheelFilter继续交给servlet容器。
     */
    protected boolean interpretResult(WebContext ctx, Object result);

    /**
     * 动作方法执行过程，默认是根据设置的参数转换器进行HTTP参数转换，然后传递给动作方法并且调用动作方法，返回动作结果。
     */
    protected Object executeAction(WebContext ctx, ActionInfo ai, Object action);

    /**
     * 获取HTTP地址，默认是request的servletPath，并处理了include和forward时的情况
     */
    protected String pathFor(HttpServletRequest request);

    /**
     * 进行HTTP地址重定向，返回目标地址，如果返回null意为不需要重定向。
     */
    protected String handleRewrite(String url);

    /**
     * 所有的处理过程：HTTP请求的路由，以及动作方法执行，结果对象的解释
     */
    protected void process(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain);

    /**
     * 配置动作方法映射
     */
    protected ActionBinder map(String path);

    /**
     * 配置结果解释器
     */
    protected &lt;T> ResultTypeBinder&lt;T> interpret(Class&lt;T> resultType);

    /**
     * 配置HTTP参数转换器
     */
    protected SetterBinder set(Type type);

    /**
     * 自动根据包路径进行动作方法映射
     */
    final protected void autoMap(String rootpkg);
}</pre>
    <h4>3. <b>HTTP请求路由</b></h4>
    <p>
        HTTP请求路由的功能是框架MVC部分最为重要的部分，框架根据请求地址的不同将请求传送给不同的动作方法进行执行。
    </p>
    <pre class="prettyprint">//在入口类init方法中
map("/hello").with(myapp.Hello.class, "execute");</pre>
    <p>
        上述代码将地址<code>/hello</code>映射到<code>Hello</code>类的<code>execute</code>方法，
        当客户端（浏览器）发起对此地址的访问，框架将触发此<code>execute</code>方法。
    </p>
    <p>
        框架中自带了一个简易的HTTP重定向功能，可以实现友好的HTTP地址配置。
    </p>
    <pre class="prettyprint">//在入口类init方法中
map("/book.jsp?id=$1").rest("/book/(\\d+)").with(mystore.Book, "get");</pre>
    <p>
        框架使用正则表达式实现地址转换，经过上面的代码配置，如果客户端发起对地址<code>/book/123</code>的访问，
        则框架将其重定向到<code>/book.jsp?id=123</code>的地址。调用的方法相当于：
    </p>
    <pre class="prettyprint">"/book/123".replaceAll("/book/(\\d+)", "/book.jsp?id=$1")</pre>
    <p>
        值得注意的是，如果重定向之后的地址也希望受WebWheel框架管理，则要求在<code>web.xml</code>中配置<code>filter-mapping</code>
        时加入<code>FORWARD</code>类型。
    </p>
    <h4>4. <b>动作方法</b></h4>
    <p id="action-method">
        被触发的java方法称为<span class="label label-danger">动作方法</span>，其所在的java类称为<span class="label label-danger">动作类</span>。
    </p>
    <p>
        动作方法可以是实例方法，也可以是静态方法，当动作方法为实例方法时，动作类的实例将通过<code>Main.createAction()</code>方法创建。
        所以当应用程序使用IOC/DI框架时，可通过覆写<code>Main.createAction()</code>方法达到灵活创建动作类实例的目的。
    </p>
    <p>
        动作方法和方法类可接受HTTP参数，HTTP参数通过<span class="label label-danger" id="setter">参数设置器</span>进行转换后注入到相应的注入地点，
        详见<a href="setter.html">参数注入</a>。
    </p>
    <p>
        参数设置器可以在入口类初始化函数<code>Main.init()</code>方法中进行创建和配置，框架将根据参数的类型使用不同的参数设置器。
    </p>
    <pre class="prettyprint">//在入口类init方法中
set(Date.class).by(new DateSetter());</pre>
    <p>
        上面的代码配置<code>Date</code>类型的参数使用<code>DateSetter</code>类为其进行设置。DateSetter的定义如下：
    </p>
    <pre class="prettyprint">public class DateSetter extends AbstractSetter&lt;Date> {
    DateFormat df = new SimpleDateFormat("yyyyMMdd");
    @Override
    public Date set(Object instance, Member member, Map&lt;String, Object> params, String paramName) {
        Object obj = params.get(paramName);
        if (obj instanceof String[]) {
            try {
                Date date = df.parse(((String[]) obj)[0]);
                if (member instanceof Method) {
                    ((Method) member).invoke(instance, date);
                } else if (member instanceof Field) {
                    ((Field) member).set(instance, date);
                }
                return date;
            } catch (Exception ignored) {
            }
        }
        return null;
    }
}</pre>
    <p>
        <code>set</code>方法的<code>params</code>参数代表了HTTP参数（从<code>HttpServletRequest.getParameterMap()</code>获取，并处理了文件上传）。
        <code>member</code>参数代表了注入地点，有可能是<code>Method</code>和<code>Field</code>，<code>instance</code>参数是动作类实例，
        <code>paramName</code>参数是HTTP参数的名称，默认是注入地点的名称（动作实例属性名称或setter属性名称）。
        通过代码可知，这里使用<code>SimpleDateFormat</code>将HTTP参数转换为了<code>Date</code>类型，并且设置到动作方法中。
    </p>
    <p>
        动作方法也可以拥有返回值，动作方法的返回值称为<span class="label label-danger" id="result">动作结果</span>。
        动作结果代表着返回客户端的内容，而如何将此内容展现给客户端则由<span class="label label-danger">动作结果解释器</span>完成，
        动作结果解释器可以在入口类初始化函数<code>Main.init()</code>方法中进行创建和配置，框架将根据动作结果的类型使用不同的动作结果解释器对其进行解释。
    </p>
    <pre class="prettyprint">//在入口类init方法中
interpret(Date.class).by(new DateInterpreter());</pre>
    <p>
        上面的代码配置<code>Date</code>类型的结果的解释器为<code>DateInterpreter</code>，配置之后如果动作方法返回的返回值为Date类型时，
        将使用此DateInterpreter实例对其进行解释。DateInterpreter的定义如下：
    </p>
    <pre class="prettyprint">public class DateInterpreter implements ResultInterpreter&lt;Date> {
    @Override
    public void interpret(Date result, WebContext ctx) throws IOException, ServletException {
        ctx.getResponse().getWriter().print("current time: " + result.toLocaleString());
    }
}</pre>
    <p>
        可以看出，DateInterpreter将向客户端按照本地格式打印当前的日期。
    </p>
    <p>
        框架自带了几种常用的结果解释器，详见<a href="result.html">结果解释</a>。
    </p>
    <h4>4. <b>约定自动配置</b></h4>
    <p>
        框架提供了一种自动化的路由配置方法，按照包名与HTTP路径对应的关系进行路由映射：
    </p>
    <pre class="prettyprint">//在入口类init方法中
autoMap("myapp.web");</pre>
    <p>
        此代码将扫描所有的<code>myapp.web</code>包下的所有java类，识别其中的动作方法，并映射到对应的地址。
        标记<code>cn.webwheel.Action</code>注释的java方法将被识别为动作方法。<code>Action</code>的定义如下：
    </p>
    <pre class="prettyprint">@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {

    //动作方法所映射的地址，如配置重定向，则为重定向之后的地址
    //如果为空，则地址与包路径一致（除去autoMap的根包路径），后缀为动作方法名
    //如果地址以'.'开头，则为指定地址后缀，其他遵循自动映射
    //如果地址不是以'/'开头，则映射地址为相对于当前包路径的路径
    String value() default "";

    //不进行映射，主要用于修改父类的行为
    boolean disabled() default false;

    //需要进行重定向的原始地址模板（正则表达式）
    String rest() default "";

    //HTTP参数的解码使用的文本编码
    String charset() default "";

    //文件上传时的所有文件最大尺寸
    int fileUploadSizeMax() default 0;

    //文件上传时的单个文件最大尺寸
    int fileUploadFileSizeMax() default 0;

    //参数注入的策略，可选择：1.动作方法参数 2.动作方法参数以及动作类实例属性
    //当选择自动时，则如果动作方法无参数时，使用动作类实例属性；否则使用动作方法参数
    SetterPolicy setterPolicy() default SetterPolicy.Auto;
}</pre>
    <p>
        例如进行自动映射
    </p>
    <pre class="prettyprint">//在入口类init方法中
autoMap("myapp.web1");</pre>
    <p>
        则动作类映射的表现为
    </p>
    <pre class="prettyprint">package myapp.web1.test
public class book {
    //映射地址为/test/book.html
    @Action
    public Object html();

    //映射地址为/test/updateBook.do
    @Action("updateBook.do")
    public Object update();

    //映射地址为/test/book.new
    @Action(".new")
    public Object create();

    //映射地址为/listBooks.json
    @Action("/listBooks.json")
    public static Object list();

    //映射地址为/test/book.buy，重定向原地址为/buy/123
    @Action(value = "?id=$1", rest = "/buy/(\\d+)")
    public static Object buy(int id);
}</pre>
    <div style="text-align: center">
        <a href="userguide.html">返回用户手册</a>
    </div>
</div>
</html>